/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.web.formmetadata.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.iec.edp.caf.boot.context.CAFContext;
import io.iec.edp.caf.commons.exception.CAFRuntimeException;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.commons.utils.StringUtils;
import io.iec.edp.caf.i18n.I18nContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.lang.NonNull;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.UrlPathHelper;


/**
 * 请求低代码语言包文件的拦截器，用于解决语言包分层合并问题
 */
// todo: 指定拦截路径urlPatterns，避免每个请求都走到shouldNotFilter判断。
@Slf4j
@WebFilter(urlPatterns = "/apps/*/*/web/*/*/i18n/*.json")
public class LowCodeLanguagePackageFilter extends OncePerRequestFilter {

    private HttpServletRequest request;

    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull FilterChain filterChain
    ) throws ServletException, IOException {
        this.request = request;
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);


        String displayLanguage = CAFContext.current.getDisplayLanguage();

        String path = new UrlPathHelper().getLookupPathForRequest(request);
        String languagePackagePathPattern = this.getUiLanguagePackagePath(path);

        String finalLanguage = displayLanguage;
        String finalLanguagePackagePath = String.format(languagePackagePathPattern, finalLanguage);
        Resource mainResource = findResource(finalLanguagePackagePath);
        while (mainResource == null) {
            String degradeLanguage = I18nContext.current.degradeLanguage(finalLanguage);
            if (degradeLanguage.equals(finalLanguage)) {
                break;
            }
            finalLanguage = degradeLanguage;
            finalLanguagePackagePath = String.format(languagePackagePathPattern, finalLanguage);
            mainResource = findResource(finalLanguagePackagePath);
        }

        UiLanguageResourceRequestWrapper requestWrapper = new UiLanguageResourceRequestWrapper(request, finalLanguagePackagePath);
        filterChain.doFilter(requestWrapper, responseWrapper);

        if (mainResource == null) {
            return; // 语言包不存在，不处理返回404。
        }

        String customLanguage = I18nContext.current.getCustomResourceIdentifier(finalLanguage);
        String customLanguagePackagePath = String.format(languagePackagePathPattern, customLanguage);
        Resource customResource = findResource(customLanguagePackagePath);

        String mainResourceContent = getLanguagePackageContent(mainResource);
        String customResourceContent = getLanguagePackageContent(customResource);
        if (finalLanguagePackagePath.equals(path) && customResource == null) {
            responseWrapper.copyBodyToResponse();
            return; // 语言包路径与原始路径相同，且自定义语言包不存在，则不处理返回原始内容。
        }
        String modifiedJson = mergeLanguagePackageContent(mainResourceContent, customResourceContent);

        //合并退化语言的多语资源
        String degradeLanguageContent = getDegradeLanguageContent(finalLanguage,languagePackagePathPattern);
        modifiedJson = mergeLanguagePackageContent(degradeLanguageContent,modifiedJson);

        // 设置修改后的内容
        byte[] modifiedContent = modifiedJson.getBytes(StandardCharsets.UTF_8);

        // 如果原始响应是压缩的，重新压缩
        String contentEncoding = response.getHeader("Content-Encoding");
        if (contentEncoding != null && contentEncoding.contains("gzip")) {
            modifiedContent = compressGzip(modifiedContent);
        }
        // 内容变化，需要重新设置响应头里的Content-Length，否则响应内容会被截断
        response.setHeader("Content-Length", String.valueOf(modifiedContent.length));
        response.setHeader("Content-Type", "application/json; charset=UTF-8");

        // 清空原始响应并写入修改后的内容
        response.resetBuffer();
        response.getOutputStream().write(modifiedContent);

    }


    /**
     * 获取退化语言的多语内容
     * @param finalLanguage
     * @param languagePackagePathPattern
     */
    private String getDegradeLanguageContent(String finalLanguage,String languagePackagePathPattern){
        String result = null;
        String currentLanguage = finalLanguage;

        while (true) {
            //1、获取退化语言的资源
            String degradeLanguage = I18nContext.current.degradeLanguage(currentLanguage);
            if(currentLanguage.equals(degradeLanguage)){
                break;
            }

            String degradeLanguagePackagePath = String.format(languagePackagePathPattern, degradeLanguage);
            Resource degradeResource = findResource(degradeLanguagePackagePath);

            String degradeResourceContent = getLanguagePackageContent(degradeResource);

            if (degradeResource == null || degradeResourceContent == null) {
                currentLanguage = degradeLanguage;
                continue;
            }

            //2、获取退化的语言的自定义资源
            String degradeCustomLanguage = I18nContext.current.getCustomResourceIdentifier(degradeLanguage);
            String degradeCustomLanguagePackagePath = String.format(languagePackagePathPattern, degradeCustomLanguage);
            Resource degradeCustomResource = findResource(degradeCustomLanguagePackagePath);

            String degradeCustomResourceContent = getLanguagePackageContent(degradeCustomResource);

            String modifiedJson  = mergeLanguagePackageContent(degradeResourceContent, degradeCustomResourceContent);
            result = mergeLanguagePackageContent(modifiedJson, result);

            currentLanguage = degradeLanguage;

        }

        return result;

    }

    /**
     * 只拦截低代码的前端语言包文件请求
     *
     * @param request HTTP请求
     * @return 是否不过滤，返回false时才过滤。
     */
    @Override
    public boolean shouldNotFilter(HttpServletRequest request) {
        // 从request头中获取Request-Source，如不含此属性或其值不是LowCodeTranslation则不过滤，否则继续判断请求路径其他条件
        String requestSource = request.getHeader("Request-Source");

        if (requestSource == null || !requestSource.equals("LowCodeTranslation")) {
            return true;
        }

        // 路径需符合 /apps/{domain}/{module}/web/{bo-project}/{form}/i18n/{language}.json
        // 路径规则的正则表达式为  "/apps/[^/]+/[^/]+/web/[^/]+/[^/]+/i18n/[^/]+\\.json"
        String webRegExp = ".*/apps/[^/]+/[^/]+/web/[^/]+/[^/]+/i18n/[^/]+\\.json";
        String mobRegExp = ".*/apps/[^/]+/[^/]+/mob/[^/]+/[^/]+/i18n/[^/]+\\.json";
        String webRtcRegExp = ".*/apps/[^/]+/[^/]+/web/[^/]+/[^/]+/[^/]+/[^/]+/i18n/[^/]+\\.json";
        String mobRtcRegExp = ".*/apps/[^/]+/[^/]+/mob/[^/]+/[^/]+/[^/]+/[^/]+/i18n/[^/]+\\.json";
        // 检查requestUri是否匹配正则表达式
        String path = request.getRequestURI();

        return !(path.matches(webRegExp) || path.matches(mobRegExp) || path.matches(webRtcRegExp) || path.matches(mobRtcRegExp));
    }

    /**
     * 查找指定路径的资源
     *
     * @param path 资源路径
     * @return 资源对象
     */
    private Resource findResource(String path) {
        if (path == null) {
            throw new IllegalStateException("path must be non null");
        }

        try {
            ApplicationContext applicationContext = SpringBeanUtils.getApplicationContext();
            Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, HandlerMapping.class, true, false);
            List<HandlerMapping> handlerMappings = new ArrayList<>(matchingBeans.values());
            HandlerExecutionChain handler = null;
            for (HandlerMapping mapping : handlerMappings) {
                handler = mapping.getHandler(this.request);
                if (handler != null) {
                    break;
                }
            }
            // handler 包装下，变成通过path获取Resource。
            if (handler == null || !(handler.getHandler() instanceof ResourceHttpRequestHandler)) {
                return null;
            }
            log.info("pathWithinHandlerMapping: " + path);
            return new UiLanguageResourceProvider((ResourceHttpRequestHandler) handler.getHandler()).getResource(path, this.request);
        } catch (Exception ignored) {
        }
        return null;
    }

    /**
     * 获取ui-language对应的语言包文件内容
     *
     * @param languagePackageResource ui-language对应的语言包文件资源
     * @return 语言包内容
     */
    private String getLanguagePackageContent(Resource languagePackageResource) {
        if (languagePackageResource == null) {
            return null;
        }
        // 使用 try-with-resources 自动关闭流
        try (InputStream inputStream = languagePackageResource.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
            return reader.lines().collect(Collectors.joining("\n"));
        } catch (IOException e) {
            log.error("Failed to load ui-language resource.", e);
            return null;
        }
    }

    private String getUiLanguagePackagePath(String originalPath) {
        if (originalPath == null) {
            return null;
        }
        // 替换\为/
        String filePath = originalPath.replaceAll("\\\\", "/");
        if (!filePath.contains("/i18n/")) {
            return null;
        }
        int i18nIndex = filePath.lastIndexOf("/i18n/");
        String afterI18n = filePath.substring(i18nIndex + "/i18n/".length()); // /i18n/后的内容

        int jsonIndex = afterI18n.lastIndexOf(".json");
        if (jsonIndex < 0) {
            return null;
        }

        int lastSlashIndex = afterI18n.lastIndexOf("/");

        // 构建替换后的路径
        return filePath.substring(0, i18nIndex + 6) +
                afterI18n.substring(0, lastSlashIndex + 1) +
                "%s" +
                afterI18n.substring(jsonIndex);
    }

    private String mergeLanguagePackageContent(String jsonContent, String customLanguageContent) {
        if (StringUtils.isEmpty(jsonContent)) {
            return customLanguageContent;
        }
        if (StringUtils.isEmpty(customLanguageContent)) {
            return jsonContent;
        }
        // 合并语言包，把uiLanguagePackageContent中存在的属性值覆盖到jsonContent中
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            JsonNode originalJson = objectMapper.readTree(jsonContent);
            JsonNode uiLanguagePackageJson = objectMapper.readTree(customLanguageContent);
            if (originalJson == null || uiLanguagePackageJson == null) {
                return jsonContent;
            }
            if (!originalJson.isObject() || !uiLanguagePackageJson.isObject()) {
                return jsonContent;
            }
            JsonNode mergedJsonNodes = mergeJsonNodes(originalJson, uiLanguagePackageJson);
            return objectMapper.writeValueAsString(mergedJsonNodes);
        } catch (JsonProcessingException e) {
            throw new CAFRuntimeException("pfcommon", "", "", e); // todo: 换成多语的抛异常方式。
        }
    }

    private JsonNode mergeJsonNodes(JsonNode mainNode, JsonNode updateNode) {
        if (!mainNode.isObject() || !updateNode.isObject()) {
            return updateNode;
        }

        ObjectNode mergedNode = mainNode.deepCopy();

        updateNode.fieldNames().forEachRemaining(fieldName -> {
            JsonNode mainField = mainNode.get(fieldName);
            JsonNode updateField = updateNode.get(fieldName);

            if (mainField != null && mainField.isObject() && updateField.isObject()) {
                JsonNode mergedField = mergeJsonNodes(mainField, updateField);
                mergedNode.set(fieldName, mergedField);
            } else {
                mergedNode.set(fieldName, updateField);
            }
        });

        return mergedNode;
    }

    private byte[] compressGzip(byte[] content) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (GZIPOutputStream gos = new GZIPOutputStream(baos)) {
            gos.write(content);
        }
        return baos.toByteArray();
    }
}

